package tech.cn.note.utils;

import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;


import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static cn.hutool.core.collection.CollUtil.isEmpty;
import static cn.hutool.core.text.CharSequenceUtil.removeAll;
import static java.math.BigDecimal.ZERO;
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.removeEnd;

/**
 * BigDecimal 辅助类
 */
@Slf4j
public class BigDecimalUtil {

    /**
     * 常量 100
     */
    public static final BigDecimal HUNDRED = new BigDecimal(100);

    /**
     * 常量 1000
     */
    public static final BigDecimal THOUSAND = new BigDecimal(1000);

    /**
     * 常量 10000
     */
    public static final BigDecimal TEN_THOUSAND = new BigDecimal(10000);

    /**
     * 默认舍入模式
     **/
    public static final RoundingMode DEFAULT_ROUNDING_MODE = RoundingMode.HALF_UP;

    public static ConcurrentHashMap<Integer, DecimalFormat> THOUSAND_FORMAT_MAP = new ConcurrentHashMap<>();

    /**
     * 若为 null 则返回 0，否则返回原数字
     */
    
    public static BigDecimal nullToZero(BigDecimal num) {
        return num == null ? ZERO : num;
    }

    /**
     * 求和，如果两个参数都为null 则返回null
     * <p>
     */
    
    public static BigDecimal addIfNull(BigDecimal x, BigDecimal y) {
        if (x == null) {
            return y;
        }
        return y == null ? x : x.add(y);
    }

    /**
     * 求和，null 认为是 0
     * <p>
     * 单独写这个方法是因为其性能比不限入参个数的那个方法要好很多
     */
    
    public static BigDecimal add(BigDecimal x, BigDecimal y) {
        if (x == null) {
            return y == null ? ZERO : y;
        }

        return y == null ? x : x.add(y);
    }

    /**
     * 求和，null 认为是 0
     * <p>
     * 单独写这个方法是因为其性能比不限入参个数的那个方法要好很多
     */
    
    public static BigDecimal add(BigDecimal x, BigDecimal y, BigDecimal z) {
        if (x == null) {
            return add(y, z);
        }

        if (y == null) {
            // x 不为 null，y 为 null，只需判断 z
            return z == null ? x : x.add(z);
        }

        if (z == null) {
            // x 不为 null，y 不为 null，直接加了返回
            return x.add(y);
        }

        // 都不为 null，直接加了返回
        return x.add(y).add(z);
    }

    /**
     * 求和，null 认为是 0
     */
    
    public static BigDecimal add(BigDecimal... nums) {
        return NumberUtil.add(nums);
    }

    /**
     * 求和，null 认为是 0
     */
    
    public static BigDecimal add(Collection<BigDecimal> nums) {
        return add(nums, Function.identity());
    }

    /**
     * 从传入的对象列表中取出数字列表并求和，null 认为是 0
     * <p>
     * 针对对象个数在 3 个及以下的列表特别做了性能优化，且即使是 3 个以上，性能也明显优于用 stream 的 reduce 方法
     * <p>
     * 注意虽然取出的数字为 null 时不会抛异常，但如果 valueExtractor 内部发生了异常依然会往外抛
     */
    @SuppressWarnings("unchecked")
    
    public static <V> BigDecimal add(Collection<V> list,  Function<V, BigDecimal> valueExtractor) {
        if (isEmpty(list)) {
            return ZERO;
        }

        if (list.size() <= 3) {
            Object[] array = list.toArray();

            if (array.length == 1) {
                return nullToZero(valueExtractor.apply((V) array[0]));
            }

            if (array.length == 2) {
                return BigDecimalUtil.add(valueExtractor.apply((V) array[0]), valueExtractor.apply((V) array[1]));
            }

            if (array.length == 3) {
                return BigDecimalUtil.add(valueExtractor.apply((V) array[0]), valueExtractor.apply((V) array[1]), valueExtractor.apply((V) array[2]));
            }
        }

        BigDecimal[] numbers = new BigDecimal[list.size()];
        int index = 0;
        for (V value : list) {
            numbers[index++] = valueExtractor.apply(value);
        }

        return NumberUtil.add(numbers);
    }

    /**
     * 从传入的对象列表中取出数字列表并求和，null 认为是 0
     * <p>
     * 针对对象个数在 3 个及以下的列表做了性能优化，且即使是 3 个以上，性能也明显优于用 stream 的 reduce 方法
     */
    
    public static <V> BigDecimal add(Stream<V> stream,  Function<V, BigDecimal> valueExtractor) {
        if (stream == null) {
            return ZERO;
        }
        return add(stream.collect(Collectors.toList()), valueExtractor);
    }

    /**
     * 做减法，null 认为是 0
     */
    
    public static BigDecimal minus(BigDecimal x, BigDecimal y) {
        if (x == null) {
            return y == null ? ZERO : y.negate();
        }

        return y == null ? x : x.subtract(y);
    }

    /**
     * 求积，null 认为是 0
     */
    
    public static BigDecimal multiply(BigDecimal x, BigDecimal y) {
        return x == null || y == null ? ZERO : x.multiply(y);
    }

    /**
     * 求积，null 认为是 0
     */
    
    public static BigDecimal multiply(BigDecimal... nums) {
        return NumberUtil.mul(nums);
    }

    /**
     * 求积，入参有 null 则返回 null
     */
    
    public static BigDecimal multiplyIfNull(BigDecimal x, BigDecimal y) {
        return x == null || y == null ? null : x.multiply(y);
    }

    /**
     * 求积，入参有 null 则返回 null
     */
    
    public static BigDecimal multiplyIfNull(BigDecimal... nums) {
        if (ObjectUtils.anyNull((Object[]) nums)) {
            return null;
        }
        return NumberUtil.mul(nums);
    }

    /**
     * 取整，null 认为是 0
     */
    
    public static BigDecimal divideToIntegralValue(BigDecimal x, BigDecimal y) {
        return isZero(x) || isZero(y) ? ZERO : x.divideToIntegralValue(y);
    }

    /**
     * 取余，null 认为是 0
     */
    
    public static BigDecimal remainder(BigDecimal x, BigDecimal y) {
        return isZero(x) || isZero(y) ? ZERO : x.remainder(y);
    }

    /**
     * 做除法，被除数为 0 则返回 0，null 认为是 0
     */
    
    public static BigDecimal divide(BigDecimal x, BigDecimal y) {
        return divide(x, y, 20, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 做除法，被除数为 0 则返回 null，入参有 null 则返回 null
     */
    
    public static BigDecimal divideIfNull(BigDecimal x, BigDecimal y) {
        return x == null || isZero(y) ? null : divide(x, y, 20, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 做除法，被除数为 0 则返回 null，入参有 null 则返回 null
     */
    
    public static BigDecimal divideIfNull(BigDecimal x, BigDecimal y, int scale) {
        return x == null || isZero(y) ? null : divide(x, y, scale, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 做除法，被除数为 0 则返回 0，null 认为是 0
     */
    
    public static BigDecimal divide(BigDecimal x, BigDecimal y, int scale, RoundingMode roundingMode) {
        if (isZero(x) || isZero(y)) {
            return ZERO;
        }
        return nullToZero(
                stripTrailingZerosNoScientific(
                        x.divide(y, scale, ObjectUtil.defaultIfNull(roundingMode, DEFAULT_ROUNDING_MODE))));
    }

    /**
     * 是否为 0，null 认为是 0
     */
    public static boolean isZero(BigDecimal num) {
        return num == null || num.compareTo(ZERO) == 0;
    }

    /**
     * 是否不为 0，null 认为是 0
     */
    public static boolean isNotZero(BigDecimal num) {
        return !isZero(num);
    }

    /**
     * 是否大于 0，null 认为是 0
     */
    public static boolean isPositive(BigDecimal num) {
        return num != null && num.compareTo(ZERO) > 0;
    }

    /**
     * 是否大于等于 0，null 认为是 0
     */
    public static boolean isPositiveOrZero(BigDecimal num) {
        return !isNegative(num);
    }

    /**
     * 是否小于 0，null 认为是 0
     */
    public static boolean isNegative(BigDecimal num) {
        return num != null && num.compareTo(ZERO) < 0;
    }

    /**
     * 是否小于等于 0，null 认为是 0
     */
    public static boolean isNegativeOrZero(BigDecimal num) {
        return !isPositive(num);
    }

    /**
     * 若大于 0 则返回原数字，否则返回 0
     */
    
    public static BigDecimal positiveOrZero(BigDecimal num) {
        return isPositive(num) ? num : ZERO;
    }

    /**
     * 若小于 0 则返回原数字，否则返回 0
     */
    
    public static BigDecimal negativeOrZero(BigDecimal num) {
        return isNegative(num) ? num : ZERO;
    }

    /**
     * 取最大值，null 被跳过，若全为 null 则返回 null
     */
    
    public static BigDecimal max(BigDecimal... nums) {
        return Arrays.stream(nums).filter(Objects::nonNull).max(BigDecimal::compareTo).orElse(null);
    }

    /**
     * 取最小值，null 被跳过，若全为 null 则返回 null
     */
    
    public static BigDecimal min(BigDecimal... nums) {
        return Arrays.stream(nums).filter(Objects::nonNull).min(BigDecimal::compareTo).orElse(null);
    }

    /**
     * 取平均值，nums 为 null 或空数组时，返回 null；否则返回 nums 的平均值，null 认为是 0
     */
    
    public static BigDecimal average(BigDecimal... nums) {
        if (nums == null || nums.length == 0) {
            return null;
        }
        return divide(add(nums), BigDecimal.valueOf(nums.length));
    }

    /**
     * 舍位，传入 null 则返回 null，rounding mode 使用 DEFAULT_ROUNDING_MODE，即 HALF_UP
     */
    
    public static BigDecimal round(BigDecimal num, int scale) {
        if (num == null) {
            return null;
        }
        return num.setScale(scale, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 舍位，并舍去小数位后面的0，由于数据库数字长度最长为35位，如果传入值总位数超过35位也会被截取为35位
     * <p>
     * 传入 null 则返回 null，rounding mode 使用 DEFAULT_ROUNDING_MODE，即 HALF_UP
     */
    
    public static BigDecimal roundAndStrip(BigDecimal num, int scale) {
        if (num == null) {
            return null;
        }
        int integer = num.precision() - num.scale();
        int valueScale = scale + integer < 35 ? scale : 35 - integer;
        valueScale = Math.max(valueScale, 0);
        num = num.setScale(valueScale, DEFAULT_ROUNDING_MODE);
        return stripTrailingZerosNoScientific(num);
    }

    /**
     * 小于，传入 null 则抛异常
     */
    public static boolean lt( BigDecimal x,  BigDecimal y) {
        return x.compareTo(y) < 0;
    }

    /**
     * 等于，允许传入 null，都是 null 也是相等
     */
    public static boolean eq(BigDecimal x, BigDecimal y) {
        return NumberUtil.equals(x, y);
    }

    /**
     * 全部等于，允许传入 null，都是 null 也是相等
     */
    public static boolean eqAll(BigDecimal... nums) {
        if (nums == null || nums.length == 0) {
            return true;
        }

        BigDecimal num0 = nums[0];
        for (BigDecimal num : nums) {
            if (neq(num0, num)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 全部等于，允许传入 null，null 被跳过
     */
    public static boolean eqAllIgnoreNull(BigDecimal... nums) {
        if (nums == null || nums.length == 0) {
            return true;
        }

        BigDecimal num0 = nums[0];
        for (BigDecimal num : nums) {
            if (num == null) {
                continue;
            }
            if (num0 == null) {
                num0 = num;
            }
            if (neq(num0, num)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 不等于，允许传入 null，都是 null 也是相等
     */
    public static boolean neq(BigDecimal x, BigDecimal y) {
        return !eq(x, y);
    }

    /**
     * 大于，传入 null 则抛异常
     */
    public static boolean gt( BigDecimal x,  BigDecimal y) {
        return x.compareTo(y) > 0;
    }

    /**
     * 大于等于，传入 null 则抛异常
     */
    public static boolean ge( BigDecimal x,  BigDecimal y) {
        return x.compareTo(y) >= 0;
    }

    /**
     * 小于等于，传入 null 则抛异常
     */
    public static boolean le( BigDecimal x,  BigDecimal y) {
        return x.compareTo(y) <= 0;
    }

    /**
     * 设精度，null 认为是 0
     */
    
    public static BigDecimal setScaleNullToZero(BigDecimal value, Integer precision) {
        return setScaleNullToZero(value, precision, DEFAULT_ROUNDING_MODE);
    }

    /**
     * 设精度，null 认为是 0
     */
    
    public static BigDecimal setScaleNullToZero(BigDecimal value, Integer precision, RoundingMode roundingMode) {
        if (precision == null) {
            return nullToZero(value);
        }

        return nullToZero(value).setScale(precision, ObjectUtil.defaultIfNull(roundingMode, DEFAULT_ROUNDING_MODE));
    }


    /**
     * 设精度，传入 null 则返回 null
     */
    
    public static BigDecimal setScaleIgnoreNull(BigDecimal value, Integer precision) {
        return setScaleIgnoreNull(value, precision, DEFAULT_ROUNDING_MODE);
    }


    /**
     * 设精度，传入 null 则返回 null
     */
    
    public static BigDecimal setScaleIgnoreNull(BigDecimal value, Integer precision, RoundingMode roundingMode) {
        if (value == null || precision == null) {
            return value;
        }
        return value.setScale(precision, ObjectUtil.defaultIfNull(roundingMode, DEFAULT_ROUNDING_MODE));
    }

//    /**
//     * 设精度，传入 null 则返回 null
//     */
//
//    public static BigDecimal setScaleIgnoreNull(BigDecimal value, Integer precision, RoundingMode roundingType) {
//        return setScaleIgnoreNull(value, precision, roundingType == null?RoundingMode.HALF_UP:roundingType);
//    }

    /**
     * 请用 parse 替换
     */
    @Deprecated
    public static BigDecimal transferStrToBigDecimal(String source) {
        if (CharSequenceUtil.isEmpty(source)) {
            return null;
        }

        // 处理字符格式中逗号写法
        StringTokenizer st = new StringTokenizer(source, ",");
        StringBuilder sb = new StringBuilder();
        while (st.hasMoreTokens()) {
            sb.append(st.nextToken());
        }

        source = sb.toString();

        // 去除末尾0
        return new BigDecimal(source).stripTrailingZeros();
    }

    /**
     * 去除末尾的 0，可能导致科学计数法，传入 null 则返回 null
     */
    public static BigDecimal stripTrailingZeros(BigDecimal value) {
        return value == null ? null : value.stripTrailingZeros();
    }

    /**
     * 去除小数点后末尾的 0，且避免科学计数法，传入 null 则返回 null
     * <p>
     * 注意此方法不采用 toPlainString 再转回来的方式，因为那样性能比较差
     */
    public static BigDecimal stripTrailingZerosNoScientific(BigDecimal value) {
        if (value == null) {
            return null;
        }

        value = value.stripTrailingZeros();
        if (value.scale() < 0) {
            return value.setScale(0, DEFAULT_ROUNDING_MODE);
        }

        return value;
    }

    /**
     * 将 BigDecimal 转为字符串，传入 null 则返回 null
     */
    public static String toPlainString(BigDecimal x) {
        return x == null ? null : x.toPlainString();
    }

    /**
     * 将 BigDecimal 转为字符串同时去除末尾多余的0，传入 null 则返回 null
     */
    public static String toPlainStringAndStrip(BigDecimal x) {
        return toPlainString(stripTrailingZerosNoScientific(x));
    }

    /**
     * 将对象转为 BigDecimal，传入 null 或传入字符串为空则返回 null，若无法转换则抛异常
     */
    
    public static BigDecimal parse(Object num) {
        if (num == null) {
            return null;
        }

        if (num instanceof String) {
            if (isBlank((String) num)) {
                return null;
            }

            // 去掉逗号再转为 BigDecimal
            return new BigDecimal(removeAll((String) num, ','));
        }

        if (num instanceof BigDecimal) {
            return (BigDecimal) num;
        }

        if (num instanceof Double) {
            return BigDecimal.valueOf((Double) num);
        }

        if (num instanceof Long) {
            return BigDecimal.valueOf((Long) num);
        }

        if (num instanceof Integer) {
            return BigDecimal.valueOf((Integer) num);
        }

        return new BigDecimal(num.toString());
    }

    /**
     * 将对象转为 BigDecimal，传入 null 或传入字符串为空则返回 0，若无法转换则抛异常
     */
    
    public static BigDecimal parseOrZero(Object num) {
        return nullToZero(parse(num));
    }

    /**
     * 将对象转为 BigDecimal，传入 null 或传入字符串为空则返回 null，若无法转换也返回 null，不抛异常
     */
    
    public static BigDecimal parseNoThrow(Object num) {
        try {
            return parse(num);
        } catch (Exception e) {
            log.debug("{} 转 BigDecimal 失败: {}", num, e.getMessage());
            return null;
        }
    }

    /**
     * 取绝对值，传入 null 则返回 null
     */
    public static BigDecimal abs(BigDecimal x) {
        return x == null ? null : x.abs();
    }

    /**
     * 将字符串末尾的百分号和中间的逗号去掉（若有），转成数字后除以 100 后返回，传入 null 则返回 null，若无法转换则抛异常
     */
    
    public static BigDecimal percentToBigDecimal(Object value) {
        if (value instanceof String) {
            value = removeEnd((String) value, "%");
        }

        BigDecimal bigDecimal = parse(value);

        return bigDecimal == null ? null : stripTrailingZerosNoScientific(bigDecimal.movePointLeft(2));
    }

    /**
     * 将输入对象转为数字后乘以 100，带上百分号后返回。若无法解析则返回输入对象的 toString。本方法不会返回 null 或抛异常
     */
    
    public static String bigDecimalToPercent(Object value) {
        if (value == null) {
            return "";
        }

        BigDecimal bigDecimal = parseNoThrow(value);

        // 小数转百分数的同时，保留位数-2，但避免科学计数法
        return bigDecimal == null ? value.toString() : bigDecimal.movePointRight(2).toPlainString() + "%";
    }

    /**
     * 乘 100，高性能版，传入 null 则返回 null
     */
    public static BigDecimal multiply100(BigDecimal value) {
        return value == null ? null : value.movePointRight(2);
    }

    /**
     * 除 100，高性能版，传入 null 则返回 null
     */
    public static BigDecimal divide100(BigDecimal value) {
        return value == null ? null : value.movePointLeft(2);
    }

    /**
     * 乘 10000，高性能版，传入 null 则返回 null
     */
    public static BigDecimal multiply10000(BigDecimal value) {
        return value == null ? null : value.movePointRight(4);
    }

    /**
     * 除 10000，高性能版，传入 null 则返回 null
     */
    public static BigDecimal divide10000(BigDecimal value) {
        return value == null ? null : value.movePointLeft(4);
    }

    /**
     * 若传入 condition 为 false 则将原数字取反后返回，否则返回原数字，高性能版，传入 null 则返回 null
     */
    
    public static BigDecimal negateIfFalse(BigDecimal num, boolean condition) {
        if (condition) {
            return num;
        }

        return num == null ? null : num.negate();
    }

    /**
     * 若传入 condition 为 false 则将原数字取反后返回，否则返回原数字，高性能版，传入 null 则返回 0
     */
    
    public static BigDecimal negateOrZeroIfFalse(BigDecimal num, boolean condition) {
        return nullToZero(negateIfFalse(num, condition));
    }

    /**
     * 若传入 condition 有且只有一个为 false 则将原数字取反后返回，否则返回原数字，高性能版，传入 null 则返回 null
     */
    
    public static BigDecimal negateIfFalse(BigDecimal num, boolean condition1, boolean condition2) {
        if (condition1 ^ condition2) {
            return num == null ? null : num.negate();
        }

        return num;
    }

    /**
     * 若传入 condition 有且只有一个为 false 则将原数字取反后返回，否则返回原数字，高性能版，传入 null 则返回 0
     */
    
    public static BigDecimal negateOrZeroIfFalse(BigDecimal num, boolean condition1, boolean condition2) {
        return nullToZero(negateIfFalse(num, condition1, condition2));
    }

    public static String formatThd(BigDecimal value, int point) {
        if (value == null) {
            return "";
        }

        DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.SIMPLIFIED_CHINESE);
        String formatPattern = point == 0 ? "#,##0" : "#,##0." + StrUtil.repeat("0", point);
        DecimalFormat decimalFormat = new DecimalFormat(formatPattern, symbols);

        return decimalFormat.format(value);
    }

    public static String formatThdByPara(BigDecimal value) {
        if (value == null) {
            return "";
        }
        if (isZero(value)) {
            return value.toPlainString();
        }
        int point = value.stripTrailingZeros().scale();
        DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.SIMPLIFIED_CHINESE);
        String formatPattern = point == 0 ? "#,##0" : "#,##0." + StrUtil.repeat("0", point);
        DecimalFormat decimalFormat = new DecimalFormat(formatPattern, symbols);
        return decimalFormat.format(value);
    }

    public static String int2chineseNum(int src) {
        final String[] num = {"零", "一", "二", "三", "四", "五", "六", "七", "八", "九"};
        final String[] unit = {"", "十", "百", "千", "万", "十", "百", "千", "亿", "十", "百", "千"};
        String dst = "";
        int count = 0;
        while (src > 0) {
            dst = String.format("%s%s%s", num[src % 10], unit[count], dst);
            src = src / 10;
            count++;
        }
        return dst.replaceAll("零[千百十]", "零").replaceAll("零+万", "万")
                .replaceAll("零+亿", "亿").replaceAll("亿万", "亿零")
                .replaceAll("零+", "零").replaceAll("零$", "");
    }

    public static String percentFormat(BigDecimal value, int point) {
        if (Objects.isNull(value)) {
            return "";
        }
        value = value.setScale(point + 2, RoundingMode.HALF_UP);
        if (isZero(value)) {
            return value.toPlainString() + "%";
        }
        return multiply100(value) + "%";
    }

    public static String percentFormat(Object obj, int point) {
        if (Objects.isNull(obj)) {
            return "";
        }
        BigDecimal value = BigDecimalUtil.parse(obj);
        return percentFormat(value, point);
    }

    public static String percentFormatStripZeros(BigDecimal value, int point) {
        if (Objects.isNull(value)) {
            return "";
        }
        value = value.setScale(point + 2, RoundingMode.HALF_UP);
        if (isZero(value)) {
            return "0%";
        }
        value = value.stripTrailingZeros();
        return multiply100(value) + "%";
    }

    public static String percentFormatStripZeros(Object obj, int point) {
        if (Objects.isNull(obj)) {
            return "";
        }
        BigDecimal value = BigDecimalUtil.parse(obj);
        return percentFormatStripZeros(value, point);
    }

    public static String thousandthFormat(BigDecimal value, int point) {
        if (Objects.isNull(value)) {
            return "";
        }
        value = value.setScale(point + 3, RoundingMode.HALF_UP);
        if (isZero(value)) {
            return value.toPlainString() + "\u2030";
        }
        return value.movePointRight(3) + "\u2030";
    }

}
